﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using System.Text;

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyModel;
using Microsoft.Extensions.Logging;

namespace Jack.EntityFrameworkCore
{
    public static class JackEfCoreServiceCollectionExtensions
    {
        public static IServiceCollection AddJackDbContext<TDbContext>(this IServiceCollection services, Action<DbContextOptionsBuilder> optionsAction = null, JackDbContextOptions jackDbContextOptions = null)
             where TDbContext : JackDbContext<TDbContext>
        {
            services.AddDbContext<TDbContext>(options =>
            {
                optionsAction?.Invoke(options);
                // 全局关闭追踪
                options.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);

                if (jackDbContextOptions != null)
                {
                    if (jackDbContextOptions.AddDefaultLogger)
                    {
                        options.LogTo(Console.WriteLine, LogLevel.Information);
                        options.EnableDetailedErrors();
                        options.EnableSensitiveDataLogging();
                        //options.UseLoggerFactory(LoggerFactory.Create(builder =>
                        //{
                        //    builder.AddConsole();
                        //    builder.AddFilter(logLevel => true);

                        //    //builder.Services.TryAddSingleton<IGlobalTracerAccessor, GlobalTracerAccessor>();
                        //    //builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<ILoggerProvider, OpenTracingLoggerProvider>());
                        //    //builder.AddFilter<OpenTracingLoggerProvider>("Default", LogLevel.Information);
                        //    //builder.AddFilter<OpenTracingLoggerProvider>("Microsoft.AspNetCore.Mvc", LogLevel.Warning);
                        //}));
                    }
                }
            });

            services.AddScoped<IUnitOfWork, EfCoreUnitOfWork<TDbContext>>();
            services.AddScoped<IEfCoreUnitOfWork<TDbContext>, EfCoreUnitOfWork<TDbContext>>();

            if (jackDbContextOptions != null)
            {
                if (jackDbContextOptions.AddDefaultRepository)
                {
                    services.AddDefaultRepository<TDbContext>();
                }
            }

            return services;
        }

        private static void AddDefaultRepository<TDbContext>(this IServiceCollection services)
             where TDbContext : JackDbContext<TDbContext>
        {
            Type typeofTDbContext = typeof(TDbContext);
            Type typeofIEfCoreRepositoryGenericType = typeof(IEfCoreRepository<>).GetGenericTypeDefinition();
            Type typeofIRepositoryGenericType = typeof(IRepository<>).GetGenericTypeDefinition();
            Type typeofEfCoreRepositoryGenericType = typeof(EfCoreRepository<,>).GetGenericTypeDefinition();

            // 载入所有项目类库
            var compileLibraries = DependencyContext.Default.CompileLibraries
            .Where(x => !x.Serviceable && x.Type == "project");

            // 获取所有数据库模型
            foreach (var compileLibrary in compileLibraries)
            {
                // 加载程序集中指定的数据库类型
                var modelTypes = AssemblyLoadContext.Default.LoadFromAssemblyName(new AssemblyName(compileLibrary.Name)).GetExportedTypes()
                    .Where(type => type.GetTypeInfo().BaseType != null && !type.IsAbstract && !type.IsInterface && !type.IsGenericType && type.IsClass && IsAssignableTo<IBaseModel>(type));
                foreach (var modelType in modelTypes)
                {
                    Type typeofEfCoreRepository = typeofEfCoreRepositoryGenericType.MakeGenericType(typeofTDbContext, modelType);
                    Type typeofIRepository = typeofIRepositoryGenericType.MakeGenericType(modelType);
                    Type typeofIEfCoreRepository = typeofIEfCoreRepositoryGenericType.MakeGenericType(modelType);

                    services.AddTransient(typeofIRepository, typeofEfCoreRepository);
                    services.AddTransient(typeofIEfCoreRepository, typeofEfCoreRepository);
                }
            }
        }

        private static bool IsAssignableTo<T>(Type @this)
        {
            if (@this == null)
            {
                throw new ArgumentNullException("this");
            }

            return typeof(T).IsAssignableFrom(@this);
        }
    }
}
